#pragma once 

#include <algorithm>

#include <ObservableEvents/ObservableEventsList/ObservaleEventsList.h>
#include <ObservableEvents/ObservableEvent.h>
#include <VectorTimer/VectorTimer.h>
#include <VectorTimer/FixedVectorTimer.h>

#include "Integrators/IrradianceCache.h"
#include "../../Integrators/RadianceDetails/RadiancePoint.h"
#include "../../Networking/ObservableEvents/InsertEvent.h"
#include "../../CoreClasses/UtilClasses/ConvertionHelper.h"

using namespace Network::ObservableEvents;
using Network::VectorTimer;
using namespace Engine::Communication::Serialization;
namespace Engine
{
	namespace Networking
	{
		namespace ObservableEvents
		{
			class ICObservableEventsList : public ObservableEventsList<RadiancePoint, IrradianceCache>
			{
			public:
				
				using ObservableEventsList<RadiancePoint, IrradianceCache>::m_observableEvents;
				using ObservableEventsList<RadiancePoint, IrradianceCache>::find;
				using ObservableEventsList<RadiancePoint, IrradianceCache>::SerializeAllUuids;
				using ObservableEventsList<RadiancePoint, IrradianceCache>::GetNeededOE;
				using ObservableEventsList<RadiancePoint, IrradianceCache>::SerializeUuid;
				using ObservableEventsList<RadiancePoint, IrradianceCache>::ByteArrayToUuid;
				using ObservableEventsList<RadiancePoint, IrradianceCache>::ParseByteArrayToUuids;
				using ObservableEventsList<RadiancePoint, IrradianceCache>::DataToMerge;
				using ObservableEventsList<RadiancePoint, IrradianceCache>::MergeEvents;

				ICObservableEventsList() :ObservableEventsList(0,0)
				{}

				ICObservableEventsList(int p_peerIndex,int p_amountPeers,  IrradianceCache* p_IC) : ObservableEventsList(p_peerIndex, p_amountPeers)
				{
					m_amountPeers = p_amountPeers;
					m_IC = p_IC;
				}

				bool CheckInvalidationExists(int& p_position)
				{
					for (int index = m_observableEvents.size() - 1; index >= 0; index--)
					{
						if (m_observableEvents[index]->GetType() == 1)
						{
							p_position = index;
							return true;
						}
					}

					return false;
				}

				///-------------------------------------------------------------------------------------------------
				/// <summary>	Merge events. </summary>
				///
				/// <remarks>	Adrian, 29/10/2015. </remarks>
				///
				/// <param name="p_data">	[in,out] The data. </param>
				///-------------------------------------------------------------------------------------------------

				virtual void MergeEvents(vector<unsigned char>& p_data, int p_skip = 0)
				{
					vector<string> events;
					vector<ObservableEvent<RadiancePoint, IrradianceCache>*> updates;

					if(p_skip == 0)
						updates = DataToMerge(p_data);
					else
						updates = DataToMerge(p_data, p_skip);

					vector<ObservableEvent<RadiancePoint, IrradianceCache>*> toAdd;
					//m_observableEvents.insert(p_observableEvents);
					//std::sort(m_observableEvents.begin(), m_observableEvents.end(),preFunction);
					vector<ObservableEvent<RadiancePoint, IrradianceCache>*> toDelete;
					for (int index = 0; index < updates.size(); index++)
					{
						ObservableEvent<RadiancePoint, IrradianceCache>* currentEvent = updates[index];

						if (!find(currentEvent->GetUuid()) && currentEvent->GetObservableEventStatus())
						{
							m_highEpochCounter++;
							m_observableEvents.push_back(currentEvent);
							toAdd.push_back(currentEvent);
						}
						else
							toDelete.push_back(currentEvent);
					}

					int indexInvalidation = 0;

					std::sort(m_observableEvents.begin(), m_observableEvents.end());

					#ifdef _DEBUG
						for (int index = 1; index < m_observableEvents.size(); index++)
						{
							assert(m_observableEvents[index - 1] < m_observableEvents[index]);
						}
					#endif

					if (CheckInvalidationExists(indexInvalidation))
					{
						vector<ObservableEvent <RadiancePoint, IrradianceCache>* > temp;

						for (int index = indexInvalidation + 1; index < m_observableEvents.size(); index++)
						{
							ObservableEvent<RadiancePoint, IrradianceCache>* p_current = m_observableEvents[index];
							temp.push_back(p_current);
						}
						m_observableEvents.empty();
						//empty caching function
						for (int index = 0; index < temp.size(); index++)
						{
							temp[index]->SetCommitted(false);
							AddObservableEvent(temp[index]);
						}
					}

					for (int index = 0; index < m_observableEvents.size(); index++)
					{
						ObservableEvent<RadiancePoint, IrradianceCache>* p_current = m_observableEvents[index];
						if (!p_current->GetCommitted())
						{
							p_current->Commit(m_IC);
							p_current->SetCommitted(true);
						}
					}
				}

				///-------------------------------------------------------------------------------------------------
				/// <summary>	Merge events. </summary>
				///
				/// <remarks>	Adrian, 29/10/2015. </remarks>
				///
				/// <param name="p_inputData">	Information describing the input. </param>
				///-------------------------------------------------------------------------------------------------

				virtual void MergeEvents(string p_inputData)
				{
					vector<string> events;
					vector<ObservableEvent<RadiancePoint, IrradianceCache>*> toAdd;
					//m_observableEvents.insert(p_observableEvents);
					//std::sort(m_observableEvents.begin(), m_observableEvents.end(),preFunction);
					vector<std::string> updates;
					Tokenizer::ParseStringSentence(p_inputData, "*", updates);
					//Tokenizer::ParseStringSentence(p_inputData, ";", events);
					//vector timer sent with data

					for (int index = 0; index < updates.size(); index++)
					{
						//not always
						ObservableEvent<RadiancePoint, IrradianceCache>* currentEvent = new InsertEvent<RadiancePoint>(updates[index], m_highEpochCounter, m_IC->GetOctree());
						if (!find(currentEvent->GetUuid()) && currentEvent->GetObservableEventStatus())
						{
							m_highEpochCounter++;
							m_observableEvents.push_back(currentEvent);
							toAdd.push_back(currentEvent);
						}
						else
							delete currentEvent;
					}

					int indexInvalidation = 0;

					std::sort(m_observableEvents.begin(), m_observableEvents.end());

#ifdef _DEBUG
					for (int index = 1; index < m_observableEvents.size(); index++)
					{
						assert(m_observableEvents[index - 1] < m_observableEvents[index]);
					}
#endif

					if (CheckInvalidationExists(indexInvalidation))
					{
						vector<ObservableEvent <RadiancePoint, IrradianceCache>* > temp;

						for (int index = indexInvalidation + 1; index < m_observableEvents.size(); index++)
						{
							ObservableEvent<RadiancePoint, IrradianceCache>* p_current = m_observableEvents[index];
							temp.push_back(p_current);
						}
						m_observableEvents.empty();
						//empty caching function
						for (int index = 0; index < temp.size(); index++)
						{
							temp[index]->SetCommitted(false);
							AddObservableEvent(temp[index]);
						}
					}

					for (int index = 0; index < m_observableEvents.size(); index++)
					{
						ObservableEvent<RadiancePoint, IrradianceCache>* p_current = m_observableEvents[index];
						if (!p_current->GetCommitted())
						{
							p_current->Commit(m_IC);
							p_current->SetCommitted(true);
						}
					}
				}

				///-------------------------------------------------------------------------------------------------
				/// <summary>	Data to merge. Usually it will alays contain  a vectortimer beforehand. would be priudent to take care of it as a starting point form over here</summary>
				///
				/// <remarks>	Adrian, 29/10/2015. </remarks>
				///
				/// <param name="p_array">	The array. </param>
				///
				/// <returns>
				/// 	null if it fails, else a std::vector&lt;ObservableEvent&lt;RadiancePoint,
				/// 	IrradianceCache&gt;*&gt;
				/// </returns>
				/// 
				/// 
				/// 
				///-------------------------------------------------------------------------------------------------

				virtual std::vector<ObservableEvent<RadiancePoint, IrradianceCache>*> DataToMerge(vector<unsigned char>& p_array)
				{
					vector<ObservableEvent<RadiancePoint, IrradianceCache>*> toOutput;
					int loopLength = 0;
					int startingPoint = 0;
					ConvertionClass::ByteArrayToInt(loopLength, p_array, startingPoint, 4);
					startingPoint += 4;
						  //|type of  obs event|Num Samples|Samples   |UUID		|Time Stamp
						  //| 4 bytes		   | 4 bytes   |40 * 100  | 16 bytes| 20 bytes|
					int amountBytes = 4 + 4 + 40 * 100 + 16 + (m_amountPeers * 4 + 4);

					for (int index = 0; index < loopLength; index++)
					{
						InsertEvent<RadiancePoint, IrradianceCache>* current = new InsertEvent<RadiancePoint, IrradianceCache>();
						current->FromSerialize(p_array, startingPoint);
						current->SetLockingStructure(m_IC->GetOctree());
						toOutput.push_back(current);
						startingPoint += amountBytes;
					} 

					return toOutput;
				}


				virtual std::vector<ObservableEvent<RadiancePoint, IrradianceCache>*> DataToMerge(vector<unsigned char>& p_array, int p_startingPoint)
				{
					vector<ObservableEvent<RadiancePoint, IrradianceCache>*> toOutput;
					int loopLength = 0;
					int startingPoint = p_startingPoint;
					ConvertionClass::ByteArrayToInt(loopLength, p_array, startingPoint, 4);
					startingPoint += 4;
					//|type of  obs event|Num Samples|Samples   |UUID		|Time Stamp
					//| 4 bytes		   | 4 bytes   |40 * 100  | 16 bytes| 20 bytes|
					int amountBytes = 4 + 4 + 40 * 100 + 16 + (m_amountPeers * 4 + 4);

					for (int index = 0; index < loopLength; index++)
					{
						InsertEvent<RadiancePoint, IrradianceCache>* current = new InsertEvent<RadiancePoint, IrradianceCache>();
						current->FromSerialize(p_array, startingPoint);
						current->SetLockingStructure(m_IC->GetOctree());
						toOutput.push_back(current);
						startingPoint += amountBytes;
					}

					return toOutput;
				}


				///-------------------------------------------------------------------------------------------------
				/// <summary>	Serialize all uuids. </summary>
				///
				/// <remarks>	Adrian, 29/10/2015. </remarks>
				///
				/// <param name="p_array">	[in,out] The array. </param>
				///-------------------------------------------------------------------------------------------------

				virtual void SerializeAllUuids(vector<unsigned char>& p_array)
				{
					//add amount of uuids
					int amountUuids = m_observableEvents.size();
					ConvertionClass::ConvertInt(amountUuids, p_array);
					//add uuids
					for (int index = 0; index < m_observableEvents.size(); index++)
					{
						SerializeUuid(p_array, m_observableEvents[index]->GetUuid());
					}
				}

				void GetAllUuids(vector<unsigned char>& p_array)
				{
					//add amount of uuids
					ConvertionClass::ConvertInt(m_observableEvents.size(), p_array);
					//add uuids
					for (int index = 0; index < m_observableEvents.size(); index++)
					{
						SerializeUuid(p_array, m_observableEvents[index]->GetUuid());
					}
				}

				virtual vector<uuid> ParseByteArrayToUuids(vector<unsigned char> p_ByteArray)
				{
					vector<uuid> allUuids;
					int currentStartingPosition = 4;
					try
					{

						int amountUuids = 0;
						ConvertionClass::ByteArrayToInt(amountUuids, p_ByteArray, 0, 4);
						
						for (int uuidIndex = 0; uuidIndex < amountUuids; uuidIndex++)
						{
							ByteArrayToUuid(p_ByteArray, currentStartingPosition, allUuids);
							currentStartingPosition += 16;
						}
					}
					catch (exception ex)
					{
						cout << "Error occured at silenced bug " << p_ByteArray.size() << "---- current location " << currentStartingPosition << endl;
					}

					return allUuids;
				}

				///-------------------------------------------------------------------------------------------------
				/// <summary>	Gets needed oe. </summary>
				///
				/// <remarks>	Adrian, 29/10/2015. </remarks>
				///
				/// <param name="p_array">			[in,out] The array. </param>
				/// <param name="p_outputArray">	[in,out] Array of outputs. </param>
				///-------------------------------------------------------------------------------------------------

				virtual void GetNeededOE(vector<unsigned char>& p_array, vector<unsigned char>& p_outputArray)
				{
					vector<uuid> allNeededOe = ParseByteArrayToUuids(p_array);
					vector<unsigned char> tempOutput;
					int checkedAmount = 0;
					int amount = allNeededOe.size();
					for (int index = 0, tempLimit = amount; index < tempLimit;  index++)
					{
						ObservableEvent<RadiancePoint, IrradianceCache>* currentSample = GetObservableEvent(allNeededOe[index]);
						
						if (currentSample != nullptr)
						{
							//continue here 
							currentSample->ToSerialize(tempOutput);
							checkedAmount++;
						}
					}

					ConvertionClass::ConvertInt(checkedAmount, p_outputArray);

					int index = 0;
					for (int index = 0; index < tempOutput.size(); index++)
					{
						p_outputArray.push_back(tempOutput[index]);
					}
				}

				virtual void FindMissingOE(vector<unsigned char>& p_array, vector<unsigned char>& p_outputArray)
				{
					try
					{
						vector<uuid> allNeededOe = ParseByteArrayToUuids(p_array);

						vector<unsigned char> tempOutput;
						int checkedAmount = 0;
						int amount = allNeededOe.size();
						for (int index = 0, tempLimit = amount; index < tempLimit; index++)
						{
							if (!find(allNeededOe[index]))
							{
								SerializeUuid(tempOutput, allNeededOe[index]);
								checkedAmount++;
							}
						}


						cout << "amount of bytes in request : " << 4 + tempOutput.size() << endl;
						ConvertionClass::ConvertInt(checkedAmount, p_outputArray);

						int index = 0;
						for (int index = 0; index < tempOutput.size(); index++)
						{
							p_outputArray.push_back(tempOutput[index]);
						}
					}
					catch (exception ex)
					{

					}
				}


			private:
				IrradianceCache* m_IC;
				int m_amountPeers;
			};
		}//observable events
	}//Network
}//Engine
